Python学习笔记(十)结构化的文本文件

对于简单的文件,唯一的结构层次是间隔的行。然而有时需要更加结构化的文本,用于后续使用的程序保存数据或者向另一个程序传送数据。
结构化的文本有很多格式,区别他们的方法如下所示:

  • 分隔符,比如tab('\t'),逗号(',')或者竖线('|')。逗号分割值(CSV)就是这样的栗子
  • '<' 和 '>' 标签,例如 XML 和 HTML。
  • 标点符号,例如 JavaScript Object Notation(JSON)
  • 缩进,例如 YAML(即 YAML Ain't Markup Language 的缩写)
  • 混合的,例如各种配置文件

每一种结构化文件格式都能够至少被一种Python模块读写。


CSV:Comma-Separated Values

带分隔符的文件一般用作数据交换格式或者数据库。我们可以人工读入CSV文件,每一次读取一行,在逗号分隔符处将每行分开,并添加结果到数据结构中,例如列表或者字典。但是,最好使用标准的csv模块,因为这样切分会得到更加复杂的信息。

  • 除了逗号,还有其他可代替的分隔符:'|' 和 '\t' 很常见。
  • 有些数据会有转义字符序列,如果分隔符出现在一块区域内,则整块都要加上引号或者 在它之前加上转义字符。
  • 文件可能有不同的换行符,Unix 系统的文件使用 '\n',Microsoft 使用 '\r\n',Apple 之前使用 '\r' 而现在使用 '\n'。
  • 在第一行可以加上列名

首先读和写一个列表的行,每一行有很多列:

In [1]: import csv 
In [2]: villains = [
   ...: ['Doctor', 'No'],
   ...: ['Rosa', 'Klebb'], 
   ...: ['Mister', 'Big'],
   ...: ['Auric', 'Goldfinger'],
   ...: ['Ernst', 'Blofeld'],
   ...: ]
In [3]: with open('villains','wt') as fout:
   ...:     csvout = csv.writer(fout)
   ...:     csvout.writerows(villains)
   ...: 

于是创建了包含以下几行的文件:
Doctor,No
Rosa,Klebb
Mister,Big
Auric,Goldfinger
Ernst,Blofeld
现在,我们重新读这个文件:

In [4]: import csv 
In [5]: with open('villains','rt') as fin: 
   ...:     cin = csv.reader(fin) 
   ...:     villains = [row for row in cin]
   ...:      
In [6]: print(villains)
[['Doctor', 'No'], ['Rosa', 'Klebb'], ['Mister', 'Big'], ['Auric', 'Goldfinger'], ['Ernst', 'Blofeld']]

我们利用函数reader()创建的结构,它在通过for循环提取到的cin对象中创建每一行。
使用 reader() 和 writer() 的默认操作。每一列用逗号分开;每一行用换行符分开。
数据可以是字典的集合(a list of dictionary),不仅仅是列表的集合(a list of list)。这次使 用新函数 DictReader() 读取文件 villains,并且指定每一列的名字:

In [7]: import csv 
In [8]: with open('villains','rt') as fin:
   ...:     cin = csv.DictReader(fin,fieldnames=['first','last'])
   ...:     villains = [row for row in cin] 
   ...:      
In [9]: print(villains)
[{'last': 'No', 'first': 'Doctor'}, {'last': 'Klebb', 'first': 'Rosa'}, {'last': 'Big', 'first': 'Mister'}, {'last': 'Goldfinger', 'first': 'Auric'}, {'last': 'Blofeld', 'first': 'Ernst'}]

下面使用新函数DictWriter()重写CSV文件,同事调用writeheader()向CSV文件中第一行写入每一列的名字:

In [10]: import csv 
In [11]: villains 
Out[11]: 
[{'first': 'Doctor', 'last': 'No'},
 {'first': 'Rosa', 'last': 'Klebb'},
 {'first': 'Mister', 'last': 'Big'},
 {'first': 'Auric', 'last': 'Goldfinger'},
 {'first': 'Ernst', 'last': 'Blofeld'}]
In [13]: with open('villains','wt') as fout:
    ...:     cout = csv.DictWriter(fout,['first','last'])
    ...:     cout.writeheader()
    ...:     cout.writerows(villains) 
    ...:

于是创建了具有标题行的新文件villains:
first,last
Doctor,No
Rosa,Klebb
Mister,Big
Auric,Goldfinger
Ernst,Blofeld
回过来再读取写入的文件,忽略函数 DictReader() 调用的参数 fieldnames,把第一行的值 (first,last)作为列标签,和字典的键做匹配:

In [14]: import csv 
In [15]: with open('villains','rt') as fin:
    ...:     cin = csv.DictReader(fin)
    ...:     villains = [row for row in cin] 
    ...:      
In [16]: print(villains) 
[{'last': 'No', 'first': 'Doctor'}, {'last': 'Klebb', 'first': 'Rosa'}, {'last': 'Big', 'first': 'Mister'}, {'last': 'Goldfinger', 'first': 'Auric'}, {'last': 'Blofeld', 'first': 'Ernst'}]

XML

带分隔符的文件仅有二维的数据:行和列。如果你想在程序之间交换数据结构,需要一种 方法把层次结构、序列、集合和其他的结构编码成文本。
XML 是最突出的处理这种转换的标记(markup)格式,它使用标签(tag)分隔数据,如 下面的示例文件 menu.xml 所示:

<?xml version="1.0"?> 
<menu>
   <breakfast hours="7-11">
    <item price="$6.00">breakfast burritos</item>
    <item price="$4.00">pancakes</item>   
   </breakfast>   
   <lunch hours="11-3">
    <item price="$5.00">hamburger</item>
   </lunch> 
   <dinner hours="3-10">
    <item price="8.00">spaghetti</item>
   </dinner>
</menu>

下面是XML的一些重要特性:

  • 标签以一个 < 字符开头,例如示例中的标签 menu、breakfast、lunch、dinner 和 item;
  • 忽略空格;
  • 通常一个开始标签(例如 <menu>)跟一段其他的内容,然后是最后相匹配的结束标签, 例如 </menu>;
  • 标签之间是可以存在多级嵌套的,在本例中,标签 item 是标签 breakfast、lunch 和 dinner 的子标签,反过来,它们也是标签 menu 的子标签;
  • 可选属性(attribute)可以出现在开始标签里,例如 price 是 item 的一个属性;
  • 标签中可以包含值(value),本例中每个 item 都会有一个值,比如第二个 breakfast item 的 pancakes;
  • 如果一个命名为 thing 的标签没有内容或者子标签,它可以用一个在右尖括号的前面添 加斜杠的简单标签所表示,例如 <thing/> 代替开始和结束都存在的标签 <thing> 和 </ thing>;
  • 存放数据的位置可以是任意的——属性、值或者子标签。例如也可以把最后一个 item 标签写作 <item price ="$8.00" food ="spaghetti"/>。

XML通常用于数据传送和消息,XML的灵活性导致出现了很多方法和性能各异的Python库。
在Python中解析XML最简单的方法是使用ElementTree,下面的代码用来解析menu.xml文件以及输出一些标签和属性:

import xml.etree.ElementTree as et
tree = et.ElementTree(file='menu.xml')
root = tree.getroot()
print(root.tag)
for child in root:
    print('tag:',child.tag,'attributes:',child.attrib)
    for grandchild in child:
        print('\ttag:',grandchild.tag,'attributes:',grandchild.attrib)
print(len(root)) # 菜单选择的数目 
3
print(root[0])    # 早餐项的数目 
2
结果:
menu
tag: breakfast attributes: {'hours': '7-11'}
    tag: item attributes: {'price': '$6.00'}
    tag: item attributes: {'price': '$4.00'}
tag: lunch attributes: {'hours': '11-3'}
    tag: item attributes: {'price': '$5.00'}
tag: dinner attributes: {'hours': '3-10'}
    tag: item attributes: {'price': '8.00'}

对于嵌套列表中的每一个元素,tag 是标签字符串,attrib 是它属性的一个字典。 ElementTree 有许多查找 XML 导出数据、修改数据乃至写入 XML 文件的方法,它的文档 :https://docs.python.org/3.3/library/xml.etree.elementtree.html

其他标准的 Python XML 库如下。

  • xml.dom JavaScript 开发者比较熟悉的文档对象模型(DOM)将 Web 文档表示成层次结构,它 会把整个 XML 文件载入到内存中,同样允许你获取所有的内容。
  • xml.sax 简单的XML API或者SAX都是通过在线解析XML,不需要一次载入所有内容到内存中, 因此对于处理巨大的 XML 文件流是一个很好的选择。

JSON

JavaScript Object Notation(JSON,http://www.json.org)是源于 JavaScript 的当今很流行的 数据交换格式,它是 JavaScript 语言的一个子集,也是 Python 合法可支持的语法。对于 Python 的兼容性使得它成为程序间数据交换的较好选择。
不同于众多的XML模块,Python只有一个主要的JSON模块(json)。下面的程序将数据编码成JSON字符串,然后再把JSON字符串解码成数据。用上面XML的数据构建JSON的数据结构:

menu = \
{
"breakfast": {
         "hours": "7-11",
         "items": {
                 "breakfast burritos": "$6.00",
                 "pancakes": "$4.00"
                 }
         },
"lunch" : {
         "hours": "11-3",
         "items": {
                 "hamburger": "$5.00"
                 }
         },
"dinner": {
         "hours": "3-10",
         "items": {
                 "spaghetti": "$8.00"
                 }
         }
}

接下来使用dumps()将menu编码成JSON字符串(menu_json):

import  json
menu_json = json.dumps(menu)
print(menu_json)
{"lunch": {"hours": "11-3", "items": {"hamburger": "$5.00"}}, "dinner": {"hours": "3-10", "items": {"spaghetti": "$8.00"}}, "breakfast": {"hours": "7-11", "items": {"pancakes": "$4.00", "breakfast burritos": "$6.00"}}}

然后使用loads()把JSON字符串menu_json解析成Python的数据结构:

menu2 = json.loads(menu_json)
print(menu2)
{'lunch': {'hours': '11-3', 'items': {'hamburger': '$5.00'}}, 'dinner': {'hours': '3-10', 'items': {'spaghetti': '$8.00'}}, 'breakfast': {'hours': '7-11', 'items': {'pancakes': '$4.00', 'breakfast burritos': '$6.00'}}}

menu 和 menu2 是具有相同键值的字典,和标准的字典用法一样,得到的键的顺序是不尽相 同的。

我们可能会在编码或解析JSON对象时得到异常,包括对象的时间datetime:

In [1]: import datetime
In [2]: now = datetime.datetime.utcnow()
In [3]: now
Out[3]: datetime.datetime(2017, 1, 17, 1, 42, 57, 521828)
In [4]: import json
In [5]: json.dumps(now)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
#......(里面都是报错)
TypeError: datetime.datetime(2017, 1, 17, 1, 42, 57, 521828) is not JSON serializable

上述错误发生是因为标准JSON没有定义日期或时间类型,需要自定义处理方式。我们可以把datetime转换成JSON能够理解的类型,比如字符串或者epoch值。

In [6]: now_str = str(now)

In [7]: json.dumps(now_str)
Out[7]: '"2017-01-17 01:42:57.521828"'

In [8]: from time import mktime

In [9]: noe_epoch = int(mktime(now.timetuple()))

In [10]: json.dumps(noe_epoch)
Out[10]: '1484588577'

如果datetime值出现在正常转换后的数据中间,那么做上面的特殊转化是困难的。我们可以通过继承修改JSON的编码方式。下面为datetime修改编码方式:

In [12]: class DTEncoder(json.JSONEncoder):
    ...:     def default(self,obj):
                 #isinstance()检查obj的类型
    ...:         if isinstance(obj,datetime.datetime):
    ...:             return int(mktime(obj.timetuple()))
                 #否则是普通解码器知道的东西
    ...:         return json.JSONEncoder.default(self,obj)
    ...:      

In [13]: json.dumps(now,cls=DTEncoder)
Out[13]: '1484588577'

新类 DTEncoder 是 JSONEncoder 的一个子类。我们需要重载它的 default() 方法来增加处理 datetime 的代码。继承确保了剩下的功能与父类的一致性。
函数 isinstance() 检查 obj 是否是类 datetime.datetime 的对象,因为在 Python 中一切都 是对象,isinstance() 总是适用的:

In [14]: type(now)
Out[14]: datetime.datetime

In [15]: isinstance(now,datetime.datetime)
Out[15]: True

In [16]: type(24)
Out[16]: int

In [17]: isinstance(24,int)
Out[17]: True

In [18]: type('hey')
Out[18]: str

In [19]: isinstance('hey',str)
Out[19]: True

对于 JSON 和其他结构化的文本格式,在不需要提前知道任何东西的情况下 可以从一个文件中解析数据结构。然后使用 isinstance() 和相关类型的方法 遍历数据。例如,如果其中的一项是字典结构,可以通过 keys()、values() 和 items() 抽取内容


YAML

和JSON类似,YAML同样有键和值,但主要用于处理日期和时间这样的数据类型。
。标准的 Python 库没有处理 YAML 的模块,因此需要安装第三方库 yaml操作数据。load() 将 YAML 字符串转换为 Python 数据 结构,而 dump() 正好相反。
下面是一个YAML文件:

name:
   first: James
   last: McIntyre 
dates:
   birth: 1828-05-25
   death: 1906-03-31 
details:
   bearded: true   
   themes: [cheese, Canada]
books:
   url: http://www.gutenberg.org/files/36068/36068-h/36068-h.htm 
poems:
   - title: 'Motto'
   text: |
        Politeness, perseverance and pluck,
        To their possessor will bring good luck.
   - title: 'Canadian Charms'
   text: |       
        Here industry is not in vain,
        For we have bounteous crops of grain,
        And you behold on every field
        Of grass and roots abundant yield,
        But after all the greatest charm
        Is the snug home upon the farm,
        And stone walls now keep cattle warm.

类似余true,false,on和off的值可以转换为Python的布尔值。证书和字符串转换为Python等价的。其他语法创建列表和字典。

import yaml 
with open('mcintyre.yaml', 'rt') as fin: 
    text = fin.read() 
data = yaml.load(text) 
data['details'] 
{'themes': ['cheese', 'Canada'], 'bearded': True} 
len(data['poems']) 
2

创建的匹配这个YAML文件的数据结构超过了一层嵌套。如果我们想得到第二首诗歌的题目,需要使用dict/list/dict的引用:

data['poems'][1]['title']
'Canadian Charms'

PyYAML 可以从字符串中载入 Python 对象,但这样做是不安全的。如果导 入你不信任的 YAML,最好还是使用 safe_ load()。


使用pickle序列化

存储数据结构到一个文件中也称为序列化(serializing)。想JSON这样的格式需要定制的序列化数据的转换器。Python提供了pickle模块以特殊的二进制保存和恢复数据对象。
JSON在解析datetime对象时会出现问题,但是对于pickle就不会存在问题:

In [1]: import pickle
In [2]: import datetime
In [3]: now1 = datetime.datetime.utcnow()
In [4]: pickled = pickle.dumps(now1) 
In [5]: pickled
Out[5]: b'\x80\x03cdatetime\ndatetime\nq\x00C\n\x07\xe1\x01\x11\x06\x11\t\x04\x86\xc7q\x01\x85q\x02Rq\x03.'

In [6]: now2 = pickle.loads(pickled) 
In [7]: now1 
Out[7]: datetime.datetime(2017, 1, 17, 6, 17, 9, 296647)
In [8]: now2 
Out[8]: datetime.datetime(2017, 1, 17, 6, 17, 9, 296647)

pickle同样适用于自己定义的类和对象。现在,我们定义一个简单的类Tiny,当其对象强制转换为字符串时会返回'tiny':

In [1]: import pickle
In [2]: class Tiny():
   ...:     def __str__(self):
   ...:         return 'tiny'
   ...:      

In [3]: obj1 = Tiny()
In [4]: obj1
Out[4]: <__main__.Tiny at 0x7f9d1fedc6d8>

In [5]: str(obj1)
Out[5]: 'tiny'

In [6]: pickled = pickle.dumps(obj1) 
In [7]: pickled 
Out[7]: b'\x80\x03c__main__\nTiny\nq\x00)\x81q\x01.'

In [8]: obj2 = pickle.loads(pickled) 
In [9]: obj2
Out[9]: <__main__.Tiny at 0x7f9d1fc11470>

In [10]: str(obj2) 
Out[10]: 'tiny'

pickled是从对象obj1转换来的序列化二进制字符串。然后再把字符串还原成对象obj1的副本obj2。使用函数dump()序列化数据到文件,而函数load()用作反序列化。

因为 pickle 会创建 Python 对象,前面提到的安全问题也同样会发生,因此对于不信任的文件反序列化。

注:本文内容来自《Python语言及其应用》欢迎购买原书阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,106评论 18 139
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,303评论 6 428
  • GitHub 上有一个 Awesome - XXX 系列的资源整理,资源非常丰富,涉及面非常广。awesome-p...
    若与阅读 18,515评论 4 418
  • 环境管理管理Python版本和环境的工具。p–非常简单的交互式python版本管理工具。pyenv–简单的Pyth...
    MrHamster阅读 3,746评论 1 61
  • “我可以跟在你身后,像影子追着光梦游”我一直都是可以的…… 在那个灯光闪耀的夜晚,在那个人头攒动的夜晚,他的歌声就...
    梁一瓶阅读 270评论 0 0